(library (lib stream)
  (export make-stream

          stream-value
          stream-index
          stream-next-proc

          stream-next
          stream-get-nth-value
          stream-map
          stream-pos
          stream->list)
  (import
   (except (rnrs base) let-values map)
   (only (guile)
         lambda* λ)
    ;; structs
   (srfi srfi-9)
   ;; for functional structs (not part of srfi-9 directly)
   (srfi srfi-9 gnu))


  (define-immutable-record-type <stream>
    (make-stream val
                 index
                 previous-input
                 next-input
                 next-output)
    stream?
    ;; val is the value of the last application of the next
    ;; procedure.
    (val stream-value)
    ;; index is the current index in the stream.
    (index stream-index)
    ;; Store the previous input, to enable things like
    ;; counting up, regardless of what the output value is
    ;; and basing the next output value on another value
    ;; than the output value. For example one might want to
    ;; output all square numbers.
    (previous-input stream-previous-input)
    ;; next-input is the procedure, which gets the next
    ;; input value for next-proc.
    (next-input stream-next-input-proc)
    ;; next-proc is the procedure, which calculates the next
    ;; value.
    (next-output stream-next-output-proc))


  (define stream-next
    (λ (stream)
      (let ([val (stream-value stream)]
            [index (stream-index stream)]
            [previous-input (stream-previous-input stream)]
            [next-input-proc (stream-next-input-proc stream)]
            [next-output-proc (stream-next-output-proc stream)])
        (make-stream
         ;; The output of the stream is based on the input,
         ;; ...
         (next-output-proc
          ;; ... which is calculated using the previous
          ;; input, the previous output value, or the index
          ;; of the stream.
          (next-input-proc val index previous-input))
         (+ index 1)
         (next-input-proc val index previous-input)
         next-input-proc
         next-output-proc))))

  (define stream-get-nth-value
    (λ (stream n)
      (cond
       [(= n 0)
        (stream-value stream)]
       [else
        (stream-get-nth-value (stream-next stream)
                              (- n 1))])))

  (define stream-map
    (λ (proc base)
      (make-stream
       ;; make sure proc is already mapped to the first
       ;; value
       (proc (stream-value base))
       ;; index of the new stream starts at zero
       0
       (stream-previous-input base)
       (stream-next-input-proc base)
       ;; Wrap the base stream's function for calculating
       ;; an output value using the provided mapped proc,
       ;; effectively applying proc to every value the
       ;; base stream would produce.
       (λ (input)
         (proc
          ((stream-next-output-proc base) input))))))

  (define stream-pos
    (λ (elem a-stream)
      (let iter ([stream a-stream] [n 0])
        (cond
         [(equal? (stream-value stream) elem) n]
         [else
          (iter (stream-next stream)
                (+ n 1))]))))

  (define stream->list
    (λ (stream max-num-elems)
      (cond
       [(= max-num-elems 0) '()]
       [else
        (cons (stream-value stream)
              (stream->list (stream-next stream) (- max-num-elems 1)))]))))
